package com.school.door;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.web.multipart.MultipartFile;
import com.mhuang.school.starter.minio.core.MinioFrame;
import com.mhuang.school.starter.minio.util.ToolUtil;
import com.netsdk.lib.NetSDKLib;
import com.netsdk.lib.NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.netsdk.lib.NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.netsdk.lib.ToolKits;
import com.netsdk.lib.structure.NET_ACCESS_USER_INFO_EX;
import com.netsdk.lib.structure.NET_FLOORS_INFO;
import com.school.device.dynamic.DvIntellifHikvisonDevice;
import com.school.device.util.DataConvertUtil;
import com.school.device.util.MockMultipartFile;
import com.sun.jna.Pointer;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DoorSdks {
	private DisConnect disConnect;
	private HaveReConnect haveReConnect;// 重连接
	private NetSDKLib netsdk;
	private NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo;
	// 登陆句柄
	private com.netsdk.lib.NetSDKLib.LLong m_hLoginHandle;
	private boolean isInit = false;
	private boolean isOpenLog = false;
	private DvIntellifHikvisonDevice device;
	private MinioFrame minioFrame;
	private Map<String, String> userIdHeads;
	private Map<String,Integer> ipStatus;

	public DoorSdks(DvIntellifHikvisonDevice device,final Map<String, String> userIdHeads,Map<String,Integer> ipStatus, MinioFrame minioFrame) {
		this.disConnect = new DisConnect();
		this.haveReConnect = new HaveReConnect();// 重连接
		this.netsdk = NetSDKLib.NETSDK_INSTANCE;
		this.m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();
		// 登陆句柄
		this.m_hLoginHandle = new com.netsdk.lib.NetSDKLib.LLong(0);
		this.device = device;
		this.userIdHeads = userIdHeads;
		this.ipStatus=ipStatus;
		this.minioFrame = minioFrame;
	}

	public boolean init(NetSDKLib.fDisConnect disConnect, NetSDKLib.fHaveReConnect haveReConnect) {
		isInit = netsdk.CLIENT_Init(disConnect, null);
		if (!isInit) {
			System.out.println("Initialize SDK failed");
			log.info("Initialize SDK failed");
			return false;
		}
		// 打开日志，可选
		NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO();
		File path = new File("./sdklog/");
		if (!path.exists()) {
			path.mkdir();
		}
		String logPath = path.getAbsoluteFile().getParent() + "\\sdklog\\" + ToolKits.getDate() + ".log";
		setLog.nPrintStrategy = 0;
		setLog.bSetFilePath = 1;
		System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length);
		setLog.bSetPrintStrategy = 1;
		isOpenLog = netsdk.CLIENT_LogOpen(setLog);
		if (!isOpenLog) {
			System.err.println("Failed to open NetSDK log");
			log.error("Failed to open NetSDK log");
		}
		// 设置断线重连回调接口，设置过断线重连成功回调函数后，当设备出现断线情况，SDK内部会自动进行重连操作
		// 此操作为可选操作，但建议用户进行设置
		netsdk.CLIENT_SetAutoReconnect(haveReConnect, null);
		// 设置登录超时时间和尝试次数，可选
		int waitTime = 5000; // 登录请求响应超时时间设置为5S
		int tryTimes = 1; // 登录时尝试建立链接1次
		netsdk.CLIENT_SetConnectTime(waitTime, tryTimes);
		// 设置更多网络参数，NET_PARAM的nWaittime，nConnectTryNum成员与CLIENT_SetConnectTime
		// 接口设置的登录设备超时时间和尝试次数意义相同,可选
		NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM();
		netParam.nConnectTime = 10000; // 登录时尝试建立链接的超时时间
		netParam.nGetConnInfoTime = 3000; // 设置子连接的超时时间
		netParam.nGetDevInfoTime = 3000;// 获取设备信息超时时间，为0默认1000ms
		netsdk.CLIENT_SetNetworkParam(netParam);
		return true;
	}

	public void destory() {
		if (isOpenLog)// 关闭日志
			netsdk.CLIENT_LogClose();
		if (isInit || m_hLoginHandle.longValue() != 0)// 关闭netsdk
			netsdk.CLIENT_Cleanup();
	}

	public boolean login(String ip, int port, String username, String password) {
		NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam = new NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY();// 入参
		pstInParam.szIP = ip.getBytes();
		pstInParam.nPort = port;
		pstInParam.szUserName = username.getBytes();
		pstInParam.szPassword = password.getBytes();
		// 出参
		NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY();
		pstOutParam.stuDeviceInfo = m_stDeviceInfo;
		m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam);
		if (m_hLoginHandle.longValue() == 0) {
			System.err.printf("Login Device[%s] Port[%d]Failed. %s\n", ip, port, ToolKits.getErrorCodePrint());
			log.error("Login Device[%s] Port[%d]Failed. %s\n", ip, port, ToolKits.getErrorCodePrint());
		} else {
			System.out.println("Login Success [ " + ip + " ]");
			log.info("Login Success [ " + ip + " ]");
			ipStatus.put(ip,1);
		}
		return m_hLoginHandle.longValue() == 0 ? false : true;
	}

	public List<Map<String, Object>> start(int currentPage, int pageSize) throws IOException {
		List<Map<String, Object>> result = new ArrayList<>();
		init(disConnect, haveReConnect);		
		if(this.m_hLoginHandle.longValue() == 0) {
			if (device != null) {
				System.out.println("开始注册: ip:" + device.getDeviceIp() + " port:" + device.getDevicePort() + " userName:" + device.getAccount() + " password:" + device.getPassword() + " type:" + device.getDeviceType());
				log.info("开始注册: ip:" + device.getDeviceIp() + " port:" + device.getDevicePort() + " userName:" + device.getAccount() + " password:" + device.getPassword() + " type:" + device.getDeviceType());
				Boolean flag = login(device.getDeviceIp(), Integer.valueOf(device.getDevicePort()), device.getAccount(), device.getPassword());
				if (flag) 
					result = getList(currentPage, pageSize);
			}
		}
		destory();
		return result;
	}

	private List<Map<String, Object>> getList(int currentPage, int pageSize) throws IOException {
		List<Map<String, Object>> results = findAccessRecordByCardNo(currentPage * pageSize);
		if (results == null || results.size() == 0)
			return null;
		int currentLen = currentPage * pageSize;
		int len = results.size();
		if (len < currentLen)
			currentLen = len;
		results = results.subList((currentPage - 1) * pageSize, currentLen);
		if (results == null || results.size() == 0)
			return null;
		String ids = results.stream().map(item -> DataConvertUtil.getObjectStr(item, "userId")).filter(id->ToolUtil.isNotEmpty(id)).collect(Collectors.toSet()).stream().collect(Collectors.joining(","));
		if(ToolUtil.isNotEmpty(ids)) {
			Map<String, String> useridNames = operateAccessUserService(ids);
			String faceIds = results.stream().map(item -> DataConvertUtil.getObjectStr(item, "userId")).filter((userId) -> !userIdHeads.containsKey(userId)).filter(id->ToolUtil.isNotEmpty(id)).collect(Collectors.toSet()).stream().collect(Collectors.joining(","));
			Map<String, String> useridUrls = null;
			if (ToolUtil.isNotEmpty(faceIds))
				useridUrls = getFace(faceIds);
			// 获取人脸照片
			if (results != null && results.size() > 0)
				for (Map<String, Object> item : results) {
					String userId = DataConvertUtil.getObjectStr(item, "userId");
					if (useridNames != null && useridNames.size() > 0) {
						String userName = useridNames.get(userId);
						item.put("userName", userName);
					}
					if (useridUrls != null && useridUrls.size() > 0) {
						String url = useridUrls.get(userId);
						item.put("headUrl", url);
						if(ToolUtil.isNotEmpty(url))
							userIdHeads.put(userId, url);
					}else {
						String url =userIdHeads.get(userId);
						if(ToolUtil.isNotEmpty(url))
							item.put("headUrl",url);
					}
				}				
		}
		return results;	
	}

	// 获取人脸信息
	public Map<String, String> getFace(String userIds) throws IOException {
		// String[] userIDs = { "11111111", "luyao111" };
		String[] userIDs = userIds.split(",");
		if(userIDs==null || userIDs.length==0)
			return null;
		int nMaxCount = userIDs.length;
		NetSDKLib.NET_ACCESS_FACE_INFO[] faces = new NetSDKLib.NET_ACCESS_FACE_INFO[nMaxCount];
		for (int i = 0; i < faces.length; i++) {
			faces[i] = new NetSDKLib.NET_ACCESS_FACE_INFO();
			faces[i].nFacePhoto = 1; // 每个用户图片个数(最多2张照片)
			// 对每张照片申请内存
			faces[i].nInFacePhotoLen[0] = 200 * 1024;
			faces[i].pFacePhotos[0].pFacePhoto = new com.sun.jna.Memory(200 * 1024); // 人脸照片数据,大小不超过200K
			faces[i].pFacePhotos[0].pFacePhoto.clear(200 * 1024);
			faces[i].nInFacePhotoLen[1] = 200 * 1024;
			faces[i].pFacePhotos[1].pFacePhoto = new com.sun.jna.Memory(200 * 1024); // 人脸照片数据,大小不超过200K
			faces[i].pFacePhotos[1].pFacePhoto.clear(200 * 1024);
		}
		// 初始化
		NetSDKLib.FAIL_CODE[] failCodes = new NetSDKLib.FAIL_CODE[nMaxCount];
		for (int i = 0; i < failCodes.length; i++)
			failCodes[i] = new NetSDKLib.FAIL_CODE();
		// 人脸操作类型
		// 获取人脸信息
		int emtype = NetSDKLib.NET_EM_ACCESS_CTL_FACE_SERVICE.NET_EM_ACCESS_CTL_FACE_SERVICE_GET;
		// 入参
		NetSDKLib.NET_IN_ACCESS_FACE_SERVICE_GET stIn = new NetSDKLib.NET_IN_ACCESS_FACE_SERVICE_GET();
		stIn.nUserNum = nMaxCount;
		for (int i = 0; i < nMaxCount; i++)
			System.arraycopy(userIDs[i].getBytes(), 0, stIn.szUserIDs[i].szUserID, 0, userIDs[i].getBytes().length);
		// 出参 NET_OUT_ACCESS_FACE_SERVICE_GET
		NetSDKLib.NET_OUT_ACCESS_FACE_SERVICE_GET stOut = new NetSDKLib.NET_OUT_ACCESS_FACE_SERVICE_GET();
		stOut.nMaxRetNum = nMaxCount;
		stOut.pFaceInfo = new com.sun.jna.Memory(faces[0].size() * nMaxCount);
		stOut.pFaceInfo.clear(faces[0].size() * nMaxCount);
		stOut.pFailCode = new com.sun.jna.Memory(failCodes[0].size() * nMaxCount);
		stOut.pFailCode.clear(failCodes[0].size() * nMaxCount);
		ToolKits.SetStructArrToPointerData(faces, stOut.pFaceInfo);
		ToolKits.SetStructArrToPointerData(failCodes, stOut.pFailCode);
		stIn.write();
		stOut.write();
		Map<String, String> useridUrls = new HashMap<>();
		if (netsdk.CLIENT_OperateAccessFaceService(m_hLoginHandle, emtype, stIn.getPointer(), stOut.getPointer(), 5000)) {
			// 将获取到的结果信息转成具体的结构体
			ToolKits.GetPointerDataToStructArr(stOut.pFaceInfo, faces);
			ToolKits.GetPointerDataToStructArr(stOut.pFailCode, failCodes);
			// 打印具体信息
			// nMaxCount 几个用户
			for (int i = 0; i < nMaxCount; i++) {
				String userId = new String(faces[i].szUserID).trim();
				int nFaceData = faces[i].nFaceData;
				NetSDKLib.FACEPHOTO[] szFaceDatas = faces[i].pFacePhotos;
				int[] lens = faces[i].nOutFacePhotoLen;
				for (int n = 0; n < nFaceData; n++) {
					Pointer szFaceData = szFaceDatas[n].pFacePhoto;
					int length = lens[n];
					System.err.println("len:" + lens[n]);
					if (szFaceData != null)
						try (ByteArrayInputStream byteArrInput = new ByteArrayInputStream(szFaceData.getByteArray(0, length))) {
							if (minioFrame == null) {
								System.err.println("minio is null,file:" + getDate() + ".jpg");
								log.error("minio is null");
							} else {
								String picurl = minioFrame.upload(inputStreamToMultipartFile(byteArrInput, getDate() + ".jpg"), null);
								System.err.println(picurl);
								useridUrls.put(userId, picurl);
							}
						} catch (IOException e) {
							e.printStackTrace();
						}
				}
				System.out.println("[" + i + "]获取人脸结果 : " + failCodes[i].nFailCode);
			}
		} else {
			System.err.println("获取人脸失败, " + ToolKits.getErrorCodeShow()); // .getErrorCode()
		}
		stIn.read();
		stOut.read();
		return useridUrls;
	}

	// 根据用户 ID 获取用户信息
	public Map<String, String> operateAccessUserService(String userIds) throws UnsupportedEncodingException {
		String[] userIDs = userIds.split(",");
		if(userIDs==null || userIDs.length==0)
			return null;
		int nMaxNum = userIDs.length;
		int emtype = NetSDKLib.NET_EM_ACCESS_CTL_USER_SERVICE.NET_EM_ACCESS_CTL_USER_SERVICE_GET;
		NetSDKLib.NET_ACCESS_USER_INFO[] users = new NetSDKLib.NET_ACCESS_USER_INFO[nMaxNum];
		// 初始化返回的失败信息数组
		NetSDKLib.FAIL_CODE[] failCodes = new NetSDKLib.FAIL_CODE[nMaxNum];
		for (int i = 0; i < nMaxNum; i++) {
			NetSDKLib.NET_ACCESS_USER_INFO info = new NetSDKLib.NET_ACCESS_USER_INFO();
			int size = new NET_FLOORS_INFO().size();
			Pointer floors = new com.sun.jna.Memory(size);
			floors.clear(size);
			info.pstuFloorsEx2 = floors;
			NET_ACCESS_USER_INFO_EX pstuUserInfoEx = new NET_ACCESS_USER_INFO_EX();
			Pointer pstuUserInfo = new com.sun.jna.Memory(pstuUserInfoEx.size());
			pstuUserInfo.clear(pstuUserInfoEx.size());
			info.pstuUserInfoEx = pstuUserInfo;
			users[i] = info;
			failCodes[i] = new NetSDKLib.FAIL_CODE();
		}
		// 入参 NET_IN_ACCESS_USER_SERVICE_GET
		NetSDKLib.NET_IN_ACCESS_USER_SERVICE_GET stIn = new NetSDKLib.NET_IN_ACCESS_USER_SERVICE_GET();
		// 用户 ID 个数
		stIn.nUserNum = userIDs.length;
		// 用户 ID
		for (int i = 0; i < userIDs.length; i++)
			System.arraycopy(userIDs[i].getBytes(), 0, stIn.szUserIDs[i].szUserID, 0, userIDs[i].getBytes().length);
		// 出参 NET_OUT_ACCESS_USER_SERVICE_GET
		NetSDKLib.NET_OUT_ACCESS_USER_SERVICE_GET stOut = new NetSDKLib.NET_OUT_ACCESS_USER_SERVICE_GET();
		stOut.nMaxRetNum = nMaxNum;
		stOut.pUserInfo = new com.sun.jna.Memory(users[0].size() * nMaxNum); // 申请内存
		stOut.pUserInfo.clear(users[0].size() * nMaxNum);
		stOut.pFailCode = new com.sun.jna.Memory(failCodes[0].size() * nMaxNum); // 申请内存
		stOut.pFailCode.clear(failCodes[0].size() * nMaxNum);
		ToolKits.SetStructArrToPointerData(users, stOut.pUserInfo);
		ToolKits.SetStructArrToPointerData(failCodes, stOut.pFailCode);
		stIn.write();
		stOut.write();
		Map<String, String> useridUrls = new HashMap<>();
		if (netsdk.CLIENT_OperateAccessUserService(m_hLoginHandle, emtype, stIn.getPointer(), stOut.getPointer(), 3000)) {
			// 将指针转为具体的信息
			ToolKits.GetPointerDataToStructArr(stOut.pUserInfo, users);
			ToolKits.GetPointerDataToStructArr(stOut.pFailCode, failCodes);
			// 打印具体的信息
			for (int i = 0; i < nMaxNum; i++) {
				String userId = new String(users[i].szUserID).trim();
				String userName = new String(users[i].szName,"utf-8").trim();
				useridUrls.put(userId, userName);
			}
		} else {
			System.err.println("查询用户失败, " + ToolKits.getErrorCodePrint());// .getErrorCode()
		}
		return useridUrls;
	}

	// 查询所有记录
	@SuppressWarnings("unused")
	public List<Map<String, Object>> findAccessRecordByCardNo(int pageSize) {
		List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
		/// 查询条件
		NetSDKLib.FIND_RECORD_ACCESSCTLCARDREC_CONDITION_EX recordCondition = new NetSDKLib.FIND_RECORD_ACCESSCTLCARDREC_CONDITION_EX();
		recordCondition.bCardNoEnable = 0; // 启用卡号查询
		NetSDKLib.NET_IN_FIND_RECORD_PARAM stuFindInParam = new NetSDKLib.NET_IN_FIND_RECORD_PARAM();
		stuFindInParam.emType = NetSDKLib.EM_NET_RECORD_TYPE.NET_RECORD_ACCESSCTLCARDREC_EX;
		stuFindInParam.pQueryCondition = recordCondition.getPointer();
		/// CLIENT_FindRecord 出参
		NetSDKLib.NET_OUT_FIND_RECORD_PARAM stuFindOutParam = new NetSDKLib.NET_OUT_FIND_RECORD_PARAM();
		recordCondition.write();
		if (netsdk.CLIENT_FindRecord(m_hLoginHandle, stuFindInParam, stuFindOutParam, 5000)) {
			recordCondition.read();
			System.out.println("FindRecord Succeed" + "\n" + "FindHandle :" + stuFindOutParam.lFindeHandle);
			int count = 0; // 循环的次数
			int nFindCount = 0;
			NetSDKLib.NET_RECORDSET_ACCESS_CTL_CARDREC[] pstRecord = new NetSDKLib.NET_RECORDSET_ACCESS_CTL_CARDREC[pageSize];
			for (int i = 0; i < pageSize; i++)
				pstRecord[i] = new NetSDKLib.NET_RECORDSET_ACCESS_CTL_CARDREC();
			/// CLIENT_FindNextRecord 入参
			NetSDKLib.NET_IN_FIND_NEXT_RECORD_PARAM stuFindNextInParam = new NetSDKLib.NET_IN_FIND_NEXT_RECORD_PARAM();
			stuFindNextInParam.lFindeHandle = stuFindOutParam.lFindeHandle;
			stuFindNextInParam.nFileCount = pageSize; // 想查询的记录条数
			/// CLIENT_FindNextRecord 出参
			NetSDKLib.NET_OUT_FIND_NEXT_RECORD_PARAM stuFindNextOutParam = new NetSDKLib.NET_OUT_FIND_NEXT_RECORD_PARAM();
			stuFindNextOutParam.nMaxRecordNum = pageSize;
			stuFindNextOutParam.pRecordList = new com.sun.jna.Memory(pstRecord[0].dwSize * pageSize);
			stuFindNextOutParam.pRecordList.clear(pstRecord[0].dwSize * pageSize);
			ToolKits.SetStructArrToPointerData(pstRecord, stuFindNextOutParam.pRecordList); // 将数组内存拷贝给 Pointer 指针
			while (true) { // 循环查询
				if (netsdk.CLIENT_FindNextRecord(stuFindNextInParam, stuFindNextOutParam, 5000)) {
					ToolKits.GetPointerDataToStructArr(stuFindNextOutParam.pRecordList, pstRecord);
					for (int i = 0; i < stuFindNextOutParam.nRetRecordNum; i++) {
						nFindCount = i + count * pageSize;
						Map<String, Object> itemMap = new HashMap<>();
						// 刷卡时间
						String cardTime = pstRecord[i].stuTime.toStringTime();
						itemMap.put("cardTime", cardTime);
						String userId = new String(pstRecord[i].szUserID).trim();
						itemMap.put("userId", userId);
						String cardNo = new String(pstRecord[i].szCardNo).trim();
						itemMap.put("cardNo", cardNo);
						int doorNo = pstRecord[i].nDoor;
						itemMap.put("doorNo", doorNo);
						String direct = "";
						if (pstRecord[i].emDirection == 1) {
							direct = "进门";
						} else if (pstRecord[i].emDirection == 2) {
							direct = "出门";
						}
						itemMap.put("direct", direct);
						results.add(itemMap);
					}
					if (stuFindNextOutParam.nRetRecordNum < pageSize) {
						break;
					} else {
						count++;
					}
				} else {
					System.err.println("FindNextRecord Failed" + netsdk.CLIENT_GetLastError());
					break;
				}
			}
			netsdk.CLIENT_FindRecordClose(stuFindOutParam.lFindeHandle);
		} else {
			System.err.println("Can Not Find This Record" + String.format("0x%x", netsdk.CLIENT_GetLastError()));
		}
		return results;
	}

	public static void main(String[] args) throws IOException {
	}

	/***************************/
	private class DisConnect implements NetSDKLib.fDisConnect {
		public DisConnect() {
		}

		@Override
		public void invoke(com.netsdk.lib.NetSDKLib.LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
			System.out.printf("Device[%s] Port[%d] DisConnect!\n", pchDVRIP, nDVRPort);
			log.info("Device[%s] Port[%d] DisConnect!\n", pchDVRIP, nDVRPort);
		}
	}

	private class HaveReConnect implements NetSDKLib.fHaveReConnect {
		public HaveReConnect() {
		}

		@Override
		public void invoke(com.netsdk.lib.NetSDKLib.LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
			System.out.printf("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);
			log.info("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);
		}
	}

	private static MultipartFile inputStreamToMultipartFile(InputStream inputStream, String fileName) {
		try {
			return new MockMultipartFile(fileName, fileName, "image/jpeg", inputStream);
		} catch (IOException e) {
			log.error("输入流转文件失败:", e);
		}
		return null;
	}

	public static String getDate() {
		SimpleDateFormat simpleDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		return simpleDate.format(new java.util.Date()).replace(" ", "_").replace(":", "-");
	}
}
